## plan:
## An xml file hold 2 lists, one of jobs to run & one of jobs which have been
##   completed. The script checks the list to decide which file to run next, and
##   transfers items to the completed list when the simulation ends, also adding
##   any extra information such as start & end times. Once a simulation job has
##   been opened, using subprocess, the runner returns to the command menu.



import subprocess       # For starting FDTD jobs
import os               # For interactingwith the filesystem
import xml.dom.minidom  # For xml documents
import time             # For start/end times
import random           # For testing
import createsamplelist # For testing
from Tkinter import *    # For GUI
import tkMessageBox
import tkFileDialog
import math

class XmlPane(Frame):
    def __init__(self, master, xmldoc, xmlowner, outertagname, innertagname):
        Frame.__init__(self, master)
        self._xmldoc       = xmldoc
        self._outertagname = outertagname
        self._innertagname = innertagname
        self._xmlowner     = xmlowner
    def getcurrentindex(self):
        cur=self._listbox.curselection()
        if not cur:
            return None
        else:
            return int(cur[0])
    def getelements(self):
        # Put this in a try-catch to avoid list index errors when there are no 
        # appropriately named elements in the xml dom
        try:
            elements=self._xmldoc.getElementsByTagName(self._outertagname)[0].getElementsByTagName(self._innertagname)
        except IndexError:
            elements=None # see above note
        return elements
    def refresh(self,selectindex=None):
        print("Warning: the refresh method is not implemented in the base class!")
    def gettext(self,item,index=None):
        return item.getAttribute("file")

class XmlListPane(XmlPane):
    def __init__(self, master, xmldoc, xmlowner, outertagname, innertagname, title):
        XmlPane.__init__(self, master, xmldoc, xmlowner, outertagname, innertagname)
        self.label = Label(self, text=title)
        self.label.grid(row=0)
        
        self._scrollbar = Scrollbar(self, orient=VERTICAL)
        self._scrollbar.grid(sticky=N+S,row=1, column=1)
        
        self._listbox = Listbox(self, width=40, height=21, yscrollcommand=self._scrollbar.set)
        self._listbox.grid(row=1)
        
        self._scrollbar.config(command=self._listbox.yview)
        self.refresh(0)
    def refresh(self,selectindex=None):
        if selectindex==None:
            selectindex=self.getcurrentindex()
        self._listbox.delete(0,END)
        
        elements = self.getelements()        
        index=1;
        
        for item in elements:
            self._listbox.insert(END, self.gettext(item,index))
            index=index+1
        
        if selectindex<0:
            selectindex=0
        elif selectindex>=self._listbox.size():
            selectindex=self._listbox.size()-1
        self._listbox.select_set(selectindex)
        self._listbox.activate(selectindex)
        self._listbox.see(selectindex)
        self._listbox.update_idletasks()
    def gettext(self,item,index=None):
        string=""
        if not index==None:
            width=3 #int(math.ceil(math.log10(elements.length)))
            string = str(index).ljust(width)+" "
        return item.getAttribute("file")

class ToRunPane(XmlListPane):
    def __init__(self, master, xmldoc, xmlowner):
        XmlListPane.__init__(self, master, xmldoc, xmlowner, "torun", "FDTDjob",
            "waiting jobs")

        self._lastdir=os.path.expanduser("~")
        self.buttonframe = Frame(self)
        self.promote = Button(self.buttonframe, text="promote", command=self.promotejob)
        self.demote  = Button(self.buttonframe, text="demote",  command=self.demotejob)
        self.remove  = Button(self.buttonframe, text="cancel",  command=self.removeitem)
        self.promote.grid(row=0, column=0,sticky=E, padx=5,pady=5)
        self.demote.grid(row=0, column=1, sticky=E+W,padx=5,pady=5)
        self.remove.grid(row=0, column=2, sticky=W, padx=5,pady=5)
        self.label.grid(row=0, columnspan=2)
        self._listbox.grid(row=1, column=0)
        self._scrollbar.grid(row=1, column=1, sticky=N+S)
        self.buttonframe.grid(row=2, column=0, columnspan=2)
        self.refresh(0)
    def promotejob(self):
        self.promotedemote(True)
    def demotejob(self):
        self.promotedemote(False)
    def promotedemote(self,promote):
        if self._xmlowner.listeditpossible():
            cur=self.getcurrentindex();
            if cur==None:
                # nothing selected, so do nothing (this shouldn't occur anyway
                # really, as refresh() should select something.
                pass
            else:
                parent=self._xmldoc.getElementsByTagName(self._outertagname)[0]
                selectedNode = parent.getElementsByTagName(self._innertagname)[cur]
                if promote:                    
                    if selectedNode.previousSibling:
                        parent.insertBefore(selectedNode,selectedNode.previousSibling);
                        string=str(self._listbox.get(cur))+" promoted"
                        cur=cur-1;
                    else:
                        # selectedNode is already first in the list, so do
                        # nothing
                        pass
                else:
                    if selectedNode.nextSibling:
                        if selectedNode.nextSibling.nextSibling:
                            parent.insertBefore(selectedNode,selectedNode.nextSibling.nextSibling)
                        else:
                            parent.appendChild(selectedNode);
                        string=str(self._listbox.get(cur))+" demoted"
                        cur=cur+1;
                    else:
                        # selectedNode is already last in the list, so do
                        # nothing
                        pass
                self.refresh(cur);
    def clear(self):
        if self._xmlowner.listeditpossible():
            for parent in self._xmldoc.getElementsByTagName(self._outertagname):
                while parent.childNodes:
                    parent.removeChild(parent.childNodes[0]).unlink()
            self.refresh(0);
    def removeitem(self):
        if self._xmlowner.listeditpossible():
            cur=self.getcurrentindex();
            if cur==None:
                # nothing selected, so do nothing (this shouldn't occur anyway
                # really, as refresh() should select something.
                pass
            else:
                parent=self._xmldoc.getElementsByTagName(self._outertagname)[0]
                child = parent.getElementsByTagName(self._innertagname)[cur]
                parent.removeChild(child)
                child.unlink()
                self.refresh(cur)
    def additem(self):
        if self._xmlowner.listeditpossible():
            filename=tkFileDialog.askopenfilenames(defaultextension=".fsp", filetypes=[('lumerical simulation projects', '.fsp'),('all files', '.*')],
                                                   initialdir=self._lastdir, multiple=True, title="Pick file(s) to add to the list")
            if filename:
                if self._xmlowner.listeditpossible():
                    for f in filename:
                        job=self._xmldoc.createElement("FDTDjob")
                        job.setAttribute("file",f);
                        self._xmldoc.getElementsByTagName("torun")[0].appendChild(job)
                    self.refresh()
                self._lastdir=os.path.dirname(filename[0])
                
            
class CompletedPane(XmlListPane):
    def __init__(self, master, xmldoc, xmlowner):
        XmlListPane.__init__(self, master, xmldoc, xmlowner, "completed",
            "FDTDjob", "completed jobs")
        self.refresh(0)
    def gettext(self,item,index):
        string = XmlListPane.gettext(self,item)+" - "
        if item.getAttribute("success")=="True":
            string = string + "suceeded"
        else:
            string = string + "failed"
        return string
        
class CurrentPane(XmlPane):
    def __init__(self, master, xmldoc, xmlowner):
        XmlPane.__init__(self, master, xmldoc, xmlowner, "current", "FDTDjob")
        sampletext="C:\\Users\\Josh\\Documents\\Cambridge\\josh-phd-code\\python>..\\batch\\placeholder.bat\n"+\
                    "a s df  fsryj r d g g hjfg jsj dfsgj dfsgj fhjfj j  g ef gwrg qwrg s rg swrg srg\n"+\
                    " s fgdsobgj dfoj foidsfsp f sldkeiouswfio dsfvh sfpoh wuofygweuf rwojweid .svnq3\n"+\
                    "87r03429 819378247gt 38709t503576320896732567132-4897s wioeru 37 3t w\n"+\
                    "This is placeholder.bat\n"+"arguments were as follows:\n"+"%1==a\n"+\
                    "%2==s\n"+"%3==df\n"+"%4==fsryj\n"+"%5==r\n"+"%6==d\n"+"%7==g\n"+"%8==g\n"+\
                    "%9==hjfg\n"+"%10==jsj\n"+"%11==dfsgj\n"+"%12==dfsgj\n"+"%13==fhjfj\n"+\
                    "%14==j\n"+"%15==g\n"+"%16==ef\n"+"%17==gwrg\n"+"%18==qwrg\n"+"%19==s\n"+\
                    "%20==rg\n"+"%21==swrg\n"+"%22==srg\n"+"%23==s\n"+"%24==fgdsobgj\n"+\
                    "%25==dfoj\n"+"%26==foidsfsp\n"+"%27==f\n"+"%28==sldkeiouswfio\n"+\
                    "%29==dsfvh\n"+"%30==sfpoh\n"+"%31==wuofygweuf\n"+"%32==rwojweid\n"+\
                    "%33==.svnq387r03429\n"+"%34==819378247gt\n"+\
                    "%35==38709t503576320896732567132-4897s\n"+"%36==wioeru\n"+\
                    "%37==37\n"+"%38==3t\n"+"%39==w"
        self._titlelabel = Label(self, text="current job")
        self._joblabel = Label(self, text="blah")
        self._scrollbar=Scrollbar(self, orient=VERTICAL)
        self._text = Text(self, width=80, height=20, font=("Courier",10), bg="white", relief=SUNKEN, borderwidth=1, yscrollcommand=self._scrollbar.set)
        self._text.insert(END, sampletext)
        self._text.configure(state=DISABLED)
        self._scrollbar.configure(command=self._text.yview)        
        
        self._titlelabel.grid(row=0, columnspan=2)
        self._joblabel.grid(row=1, columnspan=2)
        self._text.grid(row=2, column=0)
        self._scrollbar.grid(row=2, column=1, sticky=N+S)
        
        self.refresh()
    def refresh(self):
        elements = self.getelements()
        for item in elements:           
            self._joblabel.configure(text=self.gettext(item))
    def gettext(self,item):
        return "running: "+item.getAttribute("file")
    
# My ListReader class
class ListReader(object):
    """Executes an external program or batch file for each of a list of files
    contained in an xml specification"""
    time_format="%d/%m/%Y %X";
    simulator="..\\batch\\placeholder.bat";
    about_text="TkListRunner is a python module written by Josh Barnes. It "+\
               "is intended to be a list processor for batch commands, "+\
               "initially for running fdtd simulation jobs, but expandable to "+\
               "any OS command." 

    def __init__(self,master):
        self._master=master
        self._command=self.simulator
        self._path = r'fdtd-jobs-list.xml'
        self._xmldoc = xml.dom.minidom.Document()
        self.readxml()
        self._picknextjob()
        
        # Build GUI        
        self.torunpane=ToRunPane(master, self._xmldoc, self)
        self.torunpane.grid(row=0,column=0,sticky=N,padx=5,pady=5)
        
        self.currentpane=CurrentPane(master, self._xmldoc, self)
        self.currentpane.grid(row=0,column=1,sticky=N,padx=5,pady=5)
        
        self.donepane=CompletedPane(master, self._xmldoc, self)
        self.donepane.grid(row=0,column=2,sticky=N,padx=5,pady=5)

        # Build menu
        menu = Menu(master)
        master.config(menu=menu)

        filemenu = Menu(menu, tearoff=0)
        menu.add_cascade(label="File", menu=filemenu)
        filemenu.add_command(label="New list", command=self.newlistfilefn)
        filemenu.add_command(label="Open list", command=self.listselectfn)
        filemenu.add_separator()
        filemenu.add_command(label="Exit", command=self.quitcommand)
        
        listmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label="List", menu=listmenu)
        listmenu.add_command(label="Add job", command=self.torunpane.additem)
        listmenu.add_command(label="Remove job", command=self.torunpane.removeitem)
        listmenu.add_command(label="Clear list", command=self.torunpane.clear)
        
        runmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label="Run", menu=runmenu)
        runmenu.add_command(label="Start", command=self.startlistfn)
        runmenu.add_command(label="Stop after current", command=self.delayedstopfn)
        runmenu.add_command(label="Stop now", command=self.emergencystopfn)

        helpmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label="Help", menu=helpmenu)
        helpmenu.add_command(label="About...", command=self.helpaboutfn)
        self.refresh()
    def refresh(self):
        self.torunpane.refresh()
        self.currentpane.refresh()
        self.donepane.refresh()
    def listeditpossible(self):
        return True
    def quitcommand(self):
        self._master.quit();        
    def helpaboutfn(self):
        tkMessageBox.showinfo("About TkListRunner",self.about_text);
    def newlistfilefn(self):
        print "called newlistfilefn"
    def listselectfn(self):
        print "called listselectfn"
    def removejobfn(self):
        print "called removejobfn"
    def emergencystopfn(self):
        print "called emergencystopfn"
    def delayedstopfn(self):
        print "called delayedstopfn"
    def startlistfn(self):
        print "called startlistfn"
    
    def currentjob(self):
        return self._currentjob.cloneNode(True);
    def readxml(self):
        newinfo = os.stat(self._path);
#        print "re-checking xml";
        self._xmldoc = xml.dom.minidom.parse(self._path);
        self._stripjunk(self._xmldoc);
        # get blocks together
        self._aggregate("torun");
        self._aggregate("succeeded");
        self._aggregate("failed");
        # adjust current running item
        self._writecurrentjob();
        return
    def _stripjunk(self,node):
        dellist=list();
        for child in node.childNodes:
            if child.nodeType in (xml.dom.Node.TEXT_NODE,):
                dellist.append(child);
            else:
                self._stripjunk(child);
        for item in dellist:
            item.parentNode.removeChild(item);
        return None
    def writexml(self):
#        print "writing new xml"
        _fileobj=open(self._path,"w");
        _fileobj.write(self._xmldoc.toprettyxml());
        _fileobj.close();
        return
    def _writecurrentjob(self):
        self._aggregate("current",True);
        # select the "current" tag in the document, creating it if it doesn't
        # yet exist
        try:
            curr_el = self._xmldoc.getElementsByTagName("current")[0];
        except IndexError:
            curr_el = self._xmldoc.createElement("current");
            self._xmldoc.childNodes[0].appendChild(curr_el);
        # add the current job into the <current> tag
        try:
            curr_el.appendChild(self._currentjob);
        except AttributeError:
            pass    # we get an attribute error if we don't have a current job jet.
        return
    def _currentjobfinished(self,success):
        # Read xml first, to check for any new jobs that were added while we
        # were running the one we've just finished
        self.readxml();
        # Write data to the finished job
        self._currentjob.setAttribute("endtime",time.strftime(ListReader.time_format));
        # Move finished job from the current to the appropriate
        # completed list
        self._currentjob.parentNode.removeChild(self._currentjob);
        if success:
            listname="succeeded";
        else:
            listname="failed";
        self._xmldoc.getElementsByTagName(listname)[0].appendChild(self._currentjob);
        # Write modified data to xml.
        self.writexml();
        return
    def _picknextjob(self):
        newjob = self._xmldoc.getElementsByTagName("torun")[0].getElementsByTagName("FDTDjob")[0];
        newjob.parentNode.removeChild(newjob);
        self._currentjob = newjob;
        self._xmldoc.getElementsByTagName("current")[0].appendChild(self._currentjob);
        return self._currentjob.cloneNode(True);
    def _aggregate(self,elementname,empty=False):
        allelements = self._xmldoc.getElementsByTagName(elementname);
        if allelements.length>0:
            if empty:
                start=0;
            else:
                start=1;
            for element in allelements[start:allelements.length]:
                while element.childNodes.length:
                    if empty:                        
                        element.childNodes[0].parentNode.removeChild(element.childNodes[0]);
                    else:
                        # add child to the first "elementname" element
                        allelements[0].appendChild(element.childNodes[0]);
                # remove the element from the document tree
                element.parentNode.removeChild(element);
        else:
            # create an element of the appropriate name.
            allelements=[self._xmldoc.createElement(elementname)];
            self._xmldoc.childNodes[0].appendChild(allelements[0]);
        return allelements[0];
    def jobswaiting(self):
        return self._xmldoc.getElementsByTagName("torun")[0].getElementsByTagName("FDTDjob").length
    def processjob(self):        
        self._picknextjob();
        self._currentjob.setAttribute("starttime",time.strftime(ListReader.time_format));
        self.writexml();
        
        newproc = subprocess.Popen(os.path.abspath(self.simulator+" "+self._currentjob.getAttribute("file")),0,None,None,subprocess.PIPE);
##        counter = 0;
##        while not self._procflag:
##            tmr = threading.Timer(3, self._getinput);
##            tmr.start();
##            print newproc.poll()
##            if counter==9
##                self._procflag=True;
##            
        newproc.wait();
        print newproc.communicate()[0].replace("\r","");
        return bool(random.randint(0,1));        
    def processlist(self):
        while self.jobswaiting()>0:
            #print self.jobswaiting();
            success=self.processjob();
            #raw_input("this is a pause, ok?");
            self._currentjobfinished(success);

createsamplelist.refresh_list_file(1,10,7,8);
#raw_input("This is the first pause, ok?");
root=Tk();
xmllist = ListReader(root);
root.mainloop();
